package indi.zhifa.core.auth.util.impl;

import com.alibaba.fastjson2.JSON;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import indi.zhifa.core.auth.entity.AuthException;
import indi.zhifa.core.auth.interfaces.IAuthObject;
import indi.zhifa.core.auth.interfaces.ITokenObject;
import indi.zhifa.core.auth.interfaces.ITokenObjectParser;
import indi.zhifa.core.auth.properties.AuthProperties;
import indi.zhifa.core.auth.properties.JWTConfig;
import indi.zhifa.core.auth.util.ITokenUtil;
import indi.zhifa.core.common.util.CommonUtil;
import indi.zhifa.core.common.web.entity.exception.ServiceException;
import org.springframework.stereotype.Component;

import java.io.*;
import java.nio.ByteBuffer;
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.Date;

@Component
public class TokenUtilImpl implements ITokenUtil {

    final Algorithm mAlgorithm;
    final JWTVerifier mVerifier;
    final AuthProperties mAuthProperties;
    final JWTConfig mJWTConfig;
    final ThreadLocal<IAuthObject> mTokenObjectHolder;
    final ITokenObjectParser mTokenObjectParser;


    public TokenUtilImpl(AuthProperties pAuthProperties, ITokenObjectParser pTokenObjectParser) {
        mAuthProperties = pAuthProperties;
        mAlgorithm = Algorithm.HMAC256(mAuthProperties.getJwt().getSecret());
        mVerifier = JWT.require(mAlgorithm).build();
        mTokenObjectHolder = new InheritableThreadLocal<>();
        mJWTConfig = mAuthProperties.getJwt();
        mTokenObjectParser = pTokenObjectParser;
    }


    @Override
    public String encrypt(ITokenObject pTokenObject) {

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
        byte[] tokenBytes = pTokenObject.toByteArray();
        try {
            dataOutputStream.writeInt(tokenBytes.length);
            dataOutputStream.write(tokenBytes);
        }catch (IOException e){
            throw new ServiceException("序列化Protobuf时错误，"+e.getMessage());
        }
        byte[] pbData = outputStream.toByteArray();
        String encodeStr = Base64.getEncoder().encodeToString(pbData);

        LocalDateTime expireTime = LocalDateTime.now().plus(mJWTConfig.getDuration());

        String jwtStr = JWT.create()
                .withExpiresAt(CommonUtil.localDateTimeToDate(expireTime))
                .withSubject(encodeStr)
                .sign(mAlgorithm);
        String finalEncodeStr = mJWTConfig.getPrefix()+":"+jwtStr;
        return finalEncodeStr;
    }

    @Override
    public <T extends IAuthObject> T decrypt(String pToken) {

        String realToken = pToken.substring(mJWTConfig.getPrefix().length()+1);
        DecodedJWT decodedJWT;
        try{
            decodedJWT = JWT.decode(realToken);
            decodedJWT = mVerifier.verify(decodedJWT);
        }catch (TokenExpiredException tokenExpiredException){
            throw new AuthException("签名超期"+tokenExpiredException.getMessage());
        }catch (SignatureVerificationException signatureVerificationException){
            throw new AuthException("签名被篡改"+signatureVerificationException.getMessage());
        }catch (JWTVerificationException jwtVerificationException){
            throw new AuthException("签名不合法"+jwtVerificationException.getMessage());
        }
        Date expireTimeDate = decodedJWT.getExpiresAt();
        LocalDateTime expireTime = CommonUtil.dateToLocalDateTime(expireTimeDate);
        if(LocalDateTime.now().isAfter(expireTime)){
            throw new AuthException("token过期");
        }
        String subject = decodedJWT.getSubject();

        Base64.Decoder decoder = Base64.getDecoder();

        byte[] subjectBytes = decoder.decode(subject);

        InputStream inputStream = new ByteArrayInputStream(subjectBytes);

        byte[] realSubjectBytes;
        try (DataInputStream dataInputStream = new DataInputStream(inputStream)){
            int dataLen = dataInputStream.readInt();
            realSubjectBytes = new byte[dataLen];
            dataInputStream.readFully(realSubjectBytes,0,dataLen);
        }catch (IOException e){
            throw new ServiceException("鉴权时读取buffer时发生错误"+e.getMessage());
        }
        InputStream realInputStream = new ByteArrayInputStream(realSubjectBytes);
        ITokenObject tokenObject = mTokenObjectParser.parseFrom(realInputStream);
        return (T)tokenObject.toAuthObject();
    }


    @Override
    public <T extends IAuthObject> T getAuthObject() {
        return (T)mTokenObjectHolder.get();
    }

    @Override
    public void setTokenObject(IAuthObject pIAuthObject) {
        mTokenObjectHolder.set(pIAuthObject);
    }

    @Override
    public void clearTokenObject() {
        mTokenObjectHolder.remove();
    }

    @Override
    public boolean hasTokenObject() {
        return null != mTokenObjectHolder.get();
    }

}
